// NoQuake.cpp
//

#define _CRT_SECURE_NO_WARNINGS

#include "NoQuake.h"
#include "keys.h"
#include "resource.h"

HINSTANCE hInst;										// global handle to hold the application instance
HWND wndHandle;											// global variable to hold the window handle
bool keys[256];											// the keyboard, as read here
HINSTANCE hDll;
const TCHAR CLASS_NAME[] = TEXT("NoQuake");
const TCHAR VERSION_ID[] = TEXT("v1.0");

vector<int> required;									// contains the codes of the key combination

void AddTrayIcon(HWND hWnd, LPSTR szToolTip)
{
	NOTIFYICONDATA niData;
	niData.cbSize = sizeof(niData);
	niData.hWnd = hWnd;
	niData.uID = 0;
	niData.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
	niData.uCallbackMessage = NQ_TRAY_EVENT;
	niData.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_ICON_NOQUAKE));
	lstrcpy(niData.szTip, szToolTip);
	BOOL bRes = Shell_NotifyIcon(NIM_ADD, &niData);
	if (!bRes) MessageBox(hWnd, TEXT("Cannot add systray icon."), TEXT("warning"), MB_OK | MB_ICONEXCLAMATION);
}

void RemoveTrayIcon(HWND hWnd)
{
	NOTIFYICONDATA niData;
	ZeroMemory(&niData, sizeof(NOTIFYICONDATA));
	niData.cbSize = sizeof(niData);
	niData.hWnd = hWnd;
	niData.uID = 0;
	BOOL bRes = Shell_NotifyIcon(NIM_DELETE, &niData);
	if (!bRes) MessageBox(hWnd, TEXT("Cannot remove systray icon."), TEXT("warning"), MB_OK | MB_ICONEXCLAMATION);
}

// TCHAR msg[len] will get a nice formatting such as "CONTROL+ALT+Z" based
// upon the keys vector
bool GetKeyDescString(LPSTR msg, int len)
{
	if (len < 0) return false;
	int n = 0;
	lstrcpy(msg, TEXT(""));
	bool bAddPlusSign = false;
	for (int i = 0; i < 256; ++i)
		if (keys[i]) {
			if (bAddPlusSign) {
				if (n + 1 < len) {
					lstrcat(msg, TEXT("+"));
					++n;
				}
				else return false;
			}
			int x = lstrlen(vsKeyDescs[i]);
			if (n + x < len) {
				lstrcat(msg, vsKeyDescs[i]);
				n += x + 1;
			}
			else return false;
			bAddPlusSign = true;
		}
	return true;
}

/******************************************************************************
* The window procedure
******************************************************************************/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static int nKeyDown = 0;
	static int nKeyUp = 0;
	static UINT wmExplorerRestarted = 0;
	static HWND hwndEdit;
	static HWND hwndAccept;
	static bool bInsideKeyStroke = false;
	static int nKeysDown = 0;
	static SHK SetHotKeys = NULL;
	// Check any available messages from the queue
	switch (message) {
	case WM_CREATE:
		{
			ReadOptions();
			wmExplorerRestarted = RegisterWindowMessage(TEXT("TaskbarCreated"));
			hDll = LoadLibrary(TEXT("KBD.dll"));
			if (!hDll) {
				MessageBox(hWnd, TEXT("Cannot open KBD.dll, exiting."), TEXT("error"), MB_OK | MB_ICONERROR);
				exit(1);
			}
			INSTALL_PROC installer = reinterpret_cast<INSTALL_PROC>(GetProcAddress(hDll, TEXT("InstallHook")));
			SetHotKeys = reinterpret_cast<SHK>(GetProcAddress(hDll, TEXT("SetHotKeys")));
			if (installer) installer(hWnd);
			int kz[256];
			for (unsigned int i = 0; i < required.size(); ++i) kz[i] = required[i];
			SetHotKeys(static_cast<int>(required.size()), kz);
			AddTrayIcon(hWnd, TEXT("NoQuake"));
			hwndEdit = CreateWindow(TEXT("EDIT"), NULL,
				WS_CHILD | WS_VISIBLE |	WS_BORDER | ES_LEFT,
				12, 100, 180, 20, hWnd, reinterpret_cast<HMENU>(IDC_EDIT),
				((LPCREATESTRUCT)lParam)->hInstance, NULL);
			SetWindowText(hwndEdit, DEFAULT_KEY_DISPLAY);
			hwndAccept = CreateWindow (TEXT("BUTTON"), TEXT("set"),
				WS_CHILD | WS_VISIBLE | WS_DISABLED | BS_PUSHBUTTON,
				200, 98, 32, 24, hWnd, reinterpret_cast<HMENU>(IDC_BTN_ACCEPT),
				((LPCREATESTRUCT)lParam)->hInstance, NULL);

			HMENU sysMenu = GetSystemMenu(hWnd, FALSE);
			AppendMenu(sysMenu, MF_SEPARATOR, 0, 0);
			AppendMenu(sysMenu, MF_STRING, IDM_ABOUT, "About");
		}
		break;

	case WM_COMMAND:
		if (IDC_EDIT == LOWORD(wParam) && EN_SETFOCUS == HIWORD(wParam)) {
			SetWindowText(hwndEdit, CLICK_INVITATION);
			bInsideKeyStroke = true;
			for (int i = 0; i < 256; ++i) keys[i] = 0;
			nKeysDown = 0;
			SetFocus(hWnd);	// kill the focus from the edit control
		}
		else if (IDC_BTN_ACCEPT == LOWORD(wParam)) {
			EnableWindow(hwndAccept, FALSE);
			SetWindowText(hwndEdit, DEFAULT_KEY_DISPLAY);
			required.clear();
			for (int i = 0; i < 256; ++i)
				if (keys[i]) required.push_back(i);
			int kz[256];
			for (unsigned int i = 0; i < required.size(); ++i) kz[i] = required[i];
			SetHotKeys(static_cast<int>(required.size()), kz);
			InvalidateRect(hWnd, NULL, TRUE);
			SaveOptions();
		}
		else if (IDM_RESTORE == wParam) {
			SendMessage(hWnd, NQ_TRAY_EVENT, 0, WM_LBUTTONUP);
		}
		else if (IDM_EXIT == wParam) {
			PostMessage(hWnd, WM_CLOSE, 0, 0);
		}
		break;

	case WM_SYSCOMMAND:
		if (IDM_ABOUT == wParam) {
			TCHAR *versionString = new TCHAR[lstrlen(CLASS_NAME) + lstrlen(VERSION_ID) + 2];
			wsprintf(versionString, "%s %s", CLASS_NAME, VERSION_ID);
			MessageBox(hWnd,  versionString, "About", MB_ICONINFORMATION | MB_OK);
			delete []versionString;
		}
		break;

	case WM_SYSKEYDOWN:
	case WM_KEYDOWN:
		if (bInsideKeyStroke) {
			keys[wParam] = true;
			++nKeysDown;
			TCHAR msg[256];
			GetKeyDescString(msg, 256);
			SetWindowText(hwndEdit, msg);
		}
		break;

	case WM_SYSKEYUP:
	case WM_KEYUP:
		if (bInsideKeyStroke) {
			bInsideKeyStroke = false;
			EnableWindow(hwndAccept, TRUE);
			SetFocus(hwndAccept);
		}
		break;

	case NQ_TRAY_EVENT:	// a click on the tray window
		ProcessTrayNotification(hWnd, wParam, lParam);
		break;

	case WM_SIZE:
		if (SIZE_MINIMIZED == wParam) {
			ShowWindow(hWnd, SW_MINIMIZE);
			ShowWindow(hWnd, SW_HIDE);
		}

	case NQ_KBD_EVENT:
		if (wParam > 7 && wParam < 256) {
			if (0 == (static_cast<DWORD>(lParam) & (1 << 31)) && 0 == (static_cast<DWORD>(lParam) & (1 << 30))) {
				// system-wide WM_KEYDOWN
				++nKeyDown;
			}
			else if (static_cast<DWORD>(lParam) & (1 << 31)) {
				// system-wide WM_KEYUP
				++nKeyUp;
			}
			if (IsWindowVisible(hWnd)) InvalidateRect(hWnd, NULL, TRUE);
		}
		break;

	case WM_PAINT:
		{
			PAINTSTRUCT ps;
			HDC hdc = BeginPaint(hWnd, &ps);
			TCHAR msg[256];
			int len = wsprintf(msg, TEXT("%d WM_KEYDOWN"), nKeyDown);
			SetBkMode(hdc, TRANSPARENT);
			TextOut(hdc, 15, 11, msg, len);
			len = wsprintf(msg, TEXT("%d WM_KEYUP"), nKeyUp);
			TextOut(hdc, 15, 31, msg, len);
			len = wsprintf(msg, TEXT("Shortcut: "));
			TextOut(hdc, 10, 55, msg, len);
			lstrcpy(msg, TEXT(""));
			bool first = true;
			for (unsigned i = 0; i < required.size(); ++i) {
				if (!first) lstrcat(msg, TEXT("+"));
				lstrcat(msg, vsKeyDescs[required[i]]);
				first = false;
			}
			TextOut(hdc, 14, 75, msg, lstrlen(msg));
			EndPaint(hWnd, &ps);
		}
		break;

	case WM_DESTROY:
		{
			RELEASE_PROC releaser = reinterpret_cast<RELEASE_PROC>(GetProcAddress(hDll, TEXT("ReleaseHook")));
			if (NULL != releaser)
				releaser();
			BOOL bRes = FreeLibrary(hDll);
			if (!bRes) MessageBox(hWnd, TEXT("Cannot unload KBD.dll"), TEXT("error"), MB_OK | MB_ICONERROR);
			RemoveTrayIcon(hWnd);
			PostQuitMessage(0);
		}
		break;

	default:
		if (wmExplorerRestarted == message)
			AddTrayIcon(hWnd, TEXT("NoQuake"));
		break;
	}
	// Always return the message to the default window
	// procedure for further processing
	return DefWindowProc(hWnd, message, wParam, lParam);
}

void ProcessTrayNotification(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
	switch (lParam) {
	case WM_LBUTTONUP:
		if (IsWindowVisible(hWnd)) {
			ShowWindow(hWnd, SW_MINIMIZE);
			ShowWindow(hWnd, SW_HIDE);
		}
		else {
			//ShowWindow(hWnd, SW_NORMAL);
			ShowWindow(hWnd, SW_SHOW);
			ShowWindow(hWnd, SW_RESTORE);
			SetForegroundWindow(hWnd);
			SetFocus(hWnd);
		}
		break;

	case WM_RBUTTONUP:
		{
			POINT pt;
			GetCursorPos(&pt);
			HMENU sysMenu = CreatePopupMenu();
			InsertMenu(sysMenu, 0, MF_BYPOSITION | MF_STRING, IDM_RESTORE, "Toggle window");
			InsertMenu(sysMenu, 1, MF_BYPOSITION | MF_STRING, IDM_EXIT, "Exit");
			SetMenuDefaultItem(sysMenu, 0, TRUE);
			SetForegroundWindow(hWnd);
			TrackPopupMenu(sysMenu, 0, pt.x, pt.y, 0, hWnd, NULL);
		}
		break;

	default:
		// nothing to be done here
		break;
	}
}

/******************************************************************************
* InitWindow registers the window class for the application, creates the window
******************************************************************************/
bool InitWindow(HINSTANCE hInstance, int nCmdShow)
{
	WNDCLASSEX wcex;
	// Fill in the WNDCLASSEX structure. This describes how the window will look to the system
	wcex.cbSize			= sizeof(WNDCLASSEX);					// the size of the structure
	wcex.style			= CS_HREDRAW | CS_VREDRAW;				// the class style
	wcex.lpfnWndProc	= WndProc;								// the window procedure callback
	wcex.cbClsExtra		= 0;									// extra bytes to allocate for this class
	wcex.cbWndExtra		= 0;									// extra bytes to allocate for this instance
	wcex.hInstance		= hInstance;							// handle to the application instance
																// icon to associate with the application
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_ICON_NOQUAKE));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);			// the default cursor
	wcex.hbrBackground	= reinterpret_cast<HBRUSH>(GetSysColorBrush(COLOR_BTNFACE));
	wcex.lpszMenuName	= NULL;									// the resource name for the menu
	wcex.lpszClassName	= CLASS_NAME;
	wcex.hIconSm		= 0;									// the handle to the small icon

	if (!RegisterClassEx(&wcex)) {
		MessageBox(NULL, TEXT("This program requires Windows NT!"), TEXT("NoQuake"), MB_ICONERROR);
		return false;
	}
	// Create the window
	wndHandle = CreateWindow(
		TEXT("NoQuake"),		// the window class to use
		TEXT("NoQuake"),		// the title bar text
		WS_OVERLAPPED | WS_DLGFRAME | WS_SYSMENU | WS_MINIMIZEBOX,		// the window style
		CW_USEDEFAULT,									// the starting x coordinate
		CW_USEDEFAULT,									// the starting y coordinate
		252,											// the pixel width of the window
		160,											// the pixel height of the window
		NULL,											// the parent window; NULL for desktop
		NULL,											// the menu for the application; NULL for none
		hInstance,										// the handle to the application instance
		NULL											// no values passed to the window
	);

	// Make sure that the window handle that is created is valid
	if (!wndHandle) return false;

	// Display the window on the screen
	ShowWindow(wndHandle, nCmdShow);
	UpdateWindow(wndHandle);
	return true;
}

// This is winmain, the main entry point for Windows applications
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
	hInst = hInstance;
	HWND hWndPrevious = FindWindow(CLASS_NAME, NULL);
	if (NULL != hWndPrevious) {
		ShowWindow(hWndPrevious, SW_SHOW);
		ShowWindow(hWndPrevious, SW_RESTORE);
		SetForegroundWindow(hWndPrevious);
		SetFocus(hWndPrevious);
		return 0;
	}
	// Initialize the window
	if (!InitWindow(hInstance, nCmdShow)) {
		MessageBox(NULL, TEXT("Cannot create the main window. Exiting."), TEXT("NoQuake"), MB_ICONERROR);
		return 1;
	}

	// main message loop:
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0)) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	
	return static_cast<int>(msg.wParam);
}

bool ReadOptions()
{
	required.clear();
	FILE *fp = fopen("nq.ini", "rt");
	bool bOk = true;
	if (fp) {
		while (bOk && !feof(fp)) {
			int c = -1;
			fscanf(fp, "%d", &c);
			TCHAR msg[32];
			wsprintf(msg, TEXT("cetit %d"), c);
			if (-1 == c && !feof(fp))
				bOk = false;
			if (-1 != c) required.push_back(c);
		}
		fclose(fp);
		if (bOk) return true;
	}
	if (!fp || !bOk) {
		required.clear();
		required.push_back(VK_CONTROL);
		required.push_back(VK_MENU);
		required.push_back('Z');
		SaveOptions();
		return false;
	}
	return true;
}

bool SaveOptions()
{
	FILE *fp = fopen("nq.ini", "wt");
	if (fp) {
		for (unsigned i = 0; i < required.size(); ++i)
			fprintf(fp, "%d\n", required[i]);
		fclose(fp);
		return true;
	}
	return false;
}
